import {Tabs, Tab} from 'nextra-theme-docs';
import Details from 'components/Details';
import Card from 'components/Card';
import Cards from 'components/Cards';
import Callout from 'components/Callout';

# Next.js internationalized routing

<Callout>
  Routing APIs are only needed when you're using [i18n
  routing](/docs/getting-started/app-router).
</Callout>

`next-intl` integrates with the routing system of Next.js in two places:

1. [**Middleware**](/docs/routing/middleware): Negotiates the locale and handles redirects & rewrites
2. [**Navigation APIs**](/docs/routing/navigation): Provides APIs to navigate between pages

This enables you to express your app in terms of APIs like `<Link href="/about">`, while aspects like the locale and user-facing pathnames are automatically handled behind the scenes (e.g. `/de/ueber-uns`).

## Shared configuration

While the middleware provides a few more options than the navigation APIs, the majority of the configuration is shared between the two and should be used in coordination. Typically, this can be achieved by moving the shared configuration into a separate file like `src/config.ts`:

```
src
├── config.ts
├── middleware.ts
└── navigation.ts
```

This shared module can be set up like this:

```tsx filename="config.ts"
// A list of all locales that are supported
export const locales = ['en', 'de'] as const;

// ...
```

… and imported into both `middleware.ts` and `navigation.ts`:

```tsx filename="middleware.ts"
import createMiddleware from 'next-intl/middleware';
import {locales, /* ... */} from './config';

export default createMiddleware({
  locales,
  // ...

  // Used when no locale matches
  defaultLocale: 'en'
});

export const config = {
  // Match only internationalized pathnames
  matcher: ['/', '/(de|en)/:path*']
};
```

```tsx filename="src/navigation.ts"
import {createSharedPathnamesNavigation} from 'next-intl/navigation';
import {locales, /* ... */} from './config';

export const {Link, redirect, usePathname, useRouter} =
  createSharedPathnamesNavigation({locales, /* ... */});
```

### Locale prefix

By default, the pathnames of your app will be available under a prefix that matches your directory structure (e.g. `app/[locale]/about/page.tsx` → `/en/about`). You can however adapt the routing to optionally remove the prefix or customize it per locale by configuring the `localePrefix` setting.

#### Always use a locale prefix (default) [#locale-prefix-always]

By default, pathnames always start with the locale (e.g. `/en/about`).

```tsx filename="config.ts"
import {LocalePrefix} from 'next-intl/routing';

export const localePrefix = 'always' satisfies LocalePrefix;

// ...
```

<Details id="redirect-unprefixed-pathnames">
<summary>How can I redirect unprefixed pathnames?</summary>

If you want to redirect unprefixed pathnames like `/about` to a prefixed alternative like `/en/about`, you can adjust your middleware matcher to [match unprefixed pathnames](/docs/routing/middleware#matcher-no-prefix) too.

</Details>

#### Don't use a locale prefix for the default locale [#locale-prefix-as-needed]

If you only want to include a locale prefix for non-default locales, you can configure your routing accordingly:

```tsx filename="config.ts"
import {LocalePrefix} from 'next-intl/routing';

export const localePrefix = 'as-needed' satisfies LocalePrefix;

// ...
```

In this case, requests where the locale prefix matches the default locale will be redirected (e.g. `/en/about` to `/about`). This will affect both prefix-based as well as domain-based routing.

**Note that:**

1. If you use this strategy, you should make sure that your middleware matcher detects [unprefixed pathnames](/docs/routing/middleware#matcher-no-prefix).
2. If you use [the `Link` component](/docs/routing/navigation#link), the initial render will point to the prefixed version but will be patched immediately on the client once the component detects that the default locale has rendered. The prefixed version is still valid, but SEO tools might report a hint that the link points to a redirect.

#### Never use a locale prefix [#locale-prefix-never]

If you'd like to provide a locale to `next-intl`, e.g. based on user settings, you can consider setting up `next-intl` [without i18n routing](/docs/getting-started/app-router/without-i18n-routing). This way, you don't need to use the routing integration in the first place.

However, you can also configure the middleware to never show a locale prefix in the URL, which can be helpful in the following cases:

1. You're using [domain-based routing](#domains) and you support only a single locale per domain
2. You're using a cookie to determine the locale but would like to enable static rendering

```tsx filename="config.ts"
import {LocalePrefix} from 'next-intl/routing';

export const localePrefix = 'never' satisfies LocalePrefix;

// ...
```

In this case, requests for all locales will be rewritten to have the locale only prefixed internally. You still need to place all your pages inside a `[locale]` folder for the routes to be able to receive the `locale` param.

**Note that:**

1. If you use this strategy, you should make sure that your matcher detects [unprefixed pathnames](/docs/routing/middleware#matcher-no-prefix).
2. If you don't use domain-based routing, the cookie is now the source of truth for determining the locale in the middleware. Make sure that your hosting solution reliably returns the `set-cookie` header from the middleware (e.g. Vercel and Cloudflare are known to potentially [strip this header](https://developers.cloudflare.com/cache/concepts/cache-behavior/#interaction-of-set-cookie-response-header-with-cache) for cacheable requests).
3. [Alternate links](/docs/routing/middleware#alternate-links) are disabled in this mode since URLs might not be unique per locale. Due to this, consider including these yourself, or set up a [sitemap](/docs/environments/actions-metadata-route-handlers#sitemap) that links localized pages via `alternates`.

#### Custom prefixes [#locale-prefix-custom]

If you'd like to customize the user-facing prefix, you can provide a locale-based mapping:

```tsx filename="config.ts"
import {LocalePrefix} from 'next-intl/routing';

export const locales = ['en-US', 'de-AT', 'zh'] as const;

export const localePrefix = {
  mode: 'always',
  prefixes: {
    'en-US': '/us',
    'de-AT': '/eu/at'
    // (/zh will be used as-is)
  }
} satisfies LocalePrefix<typeof locales>;
```

**Note that:**

1. Custom prefixes are only visible to the user and rewritten internally to the corresponding locale. Therefore the `[locale]` segment will correspond to the locale, not the prefix.
2. You might have to adapt your [middleware matcher](/docs/routing/middleware#matcher-config) to match the custom prefixes.

<Details id="locale-prefix-custom-read-prefix">
<summary>Can I read the matched prefix in my app?</summary>

Since the custom prefix is rewritten to the locale internally, you can't access the prefix directly. However, you can extract details like the region from the locale:

```tsx
import {useLocale} from 'next-intl';

function Component() {
  // Assuming the locale is 'en-US'
  const locale = useLocale();

  // Returns 'US'
  new Intl.Locale(locale).region;
}
```

The region must be a valid [ISO 3166-1 alpha-2 country code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements) or a [UN M49 region code](https://en.wikipedia.org/wiki/UN_M49#Code_lists). When passed to `Intl.Locale`, the region code is treated as case-insensitive and normalized to uppercase. You can also combine languages with regions where the language is not natively spoken (e.g. `en-AT` describes English as used in Austria).

Apart from the region, a locale can [encode further properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale#description), like the numbering system.

If you'd like to encode custom information in the locale, you can use arbitrary [private extensions](https://tc39.es/proposal-intl-locale/#sec-insert-unicode-extension-and-canonicalize), denoted by the `-x-` prefix (e.g. `en-US-x-usd`). The `Intl.Locale` constructor ignores private extensions, but you can extract them from the locale string manually.

</Details>

### Localized pathnames [#pathnames]

Many apps choose to localize pathnames, especially when search engine optimization is relevant, e.g.:

- `/en/about`
- `/de/ueber-uns`

Since you want to define these routes only once internally, you can use the `next-intl` middleware to [rewrite](https://nextjs.org/docs/api-reference/next.config.js/rewrites) such incoming requests to shared pathnames.

```tsx filename="config.ts"
export const locales = ['en', 'de'] as const;

// The `pathnames` object holds pairs of internal and
// external paths. Based on the locale, the external
// paths are rewritten to the shared, internal ones.
export const pathnames = {
  // If all locales use the same pathname, a single
  // external path can be used for all locales
  '/': '/',
  '/blog': '/blog',

  // If locales use different paths, you can
  // specify each external path per locale
  '/about': {
    en: '/about',
    de: '/ueber-uns'
  },

  // Dynamic params are supported via square brackets
  '/news/[articleSlug]-[articleId]': {
    en: '/news/[articleSlug]-[articleId]',
    de: '/neuigkeiten/[articleSlug]-[articleId]'
  },

  // Static pathnames that overlap with dynamic segments
  // will be prioritized over the dynamic segment
  '/news/just-in': {
    en: '/news/just-in',
    de: '/neuigkeiten/aktuell'
  },

  // Also (optional) catch-all segments are supported
  '/categories/[...slug]': {
    en: '/categories/[...slug]',
    de: '/kategorien/[...slug]'
  }
} satisfies Pathnames<typeof locales>;
```

Localized pathnames map to a single internal pathname that is created via the file-system based routing in Next.js. In the example above, `/de/ueber-uns` will be handled by the page at `/[locale]/about/page.tsx`.

<Callout>
  If you're using localized pathnames, you should use
  `createLocalizedPathnamesNavigation` instead of
  `createSharedPathnamesNavigation` for your [navigation
  APIs](/docs/routing/navigation).
</Callout>

<Details id="localized-pathnames-revalidation">
<summary>How can I revalidate localized pathnames?</summary>

Depending on if a route is generated statically (at build time) or dynamically (at runtime), [`revalidatePath`](https://nextjs.org/docs/app/api-reference/functions/revalidatePath) needs to be called either for the localized or the internal pathname.

Consider this example:

```
app
└── [locale]
    └── news
        └── [slug]
```

… with this middleware configuration:

```tsx filename="middleware.ts"
import createMiddleware from 'next-intl/middleware';

export default createMiddleware({
  defaultLocale: 'en',
  locales: ['en', 'de'],
  pathnames: {
    '/news/[slug]': {
      en: '/news/[slug]',
      de: '/neuigkeiten/[slug]'
    }
  }
});
```

Depending on whether `some-article` was included in [`generateStaticParams`](https://nextjs.org/docs/app/api-reference/functions/generate-static-params) or not, you can revalidate the route like this:

```tsx
// Statically generated at build time
revalidatePath('/de/news/some-article');

// Dynamically generated at runtime:
revalidatePath('/de/neuigkeiten/some-article');
```

When in doubt, you can revalidate both paths to be on the safe side.

See also: [`vercel/next.js#59825`](https://github.com/vercel/next.js/issues/59825)

</Details>

<Details id="localized-pathnames-dynamic-segments">
<summary>How can I localize dynamic segments?</summary>

If you have a route like `/news/[articleSlug]-[articleId]`, you may want to localize the `articleSlug` part in the pathname like this:

```
/en/news/launch-of-new-product-94812
/de/neuigkeiten/produktneuheit-94812
```

In this case, the localized slug can either be provided by the backend or generated in the frontend by slugifying the localized article title.

A good practice is to include the ID in the URL, allowing you to retrieve the article based on this information from the backend. The ID can be further used to implement [self-healing URLs](https://mikebifulco.com/posts/self-healing-urls-nextjs-seo), where a redirect is added if the `articleSlug` doesn't match.

If you localize the values for dynamic segments, you might want to turn off [alternate links](/docs/routing/middleware#alternate-links) and provide your own implementation that considers localized values for dynamic segments.

</Details>

<Details id="localized-pathnames-cms">
<summary>How do I integrate with an external system like a CMS that provides localized pathnames?</summary>

In case you're using a system like a CMS to configure localized pathnames, you'll typically implement this with a dynamic segment that catches all localized pathnames _instead_ of using the `pathnames` configuration from `next-intl`.

**Examples:**

1. All pathnames are handled by your CMS: `[locale]/[[...slug]]/page.tsx`
2. Some pathnames are handled by your CMS: `[locale]/blog/[...slug]/page.tsx`

```tsx filename="page.tsx"
import {notFound} from 'next';
import {fetchContent} from './cms';

type Props = {
  params: {
    locale: string;
    slug: Array<string>;
  };
};

export default async function CatchAllPage({params}: Props) {
  const content = await fetchContent(params.locale, params.slug);
  if (!content) notFound();

  // ...
}
```

In this case, you'll likely want to disable [alternate links](/docs/routing/middleware#alternate-links) and provide your own implementation instead.

Furthermore, in case you provide a locale switcher, it might require special care to be able to switch between localized pathnames of the same page. A simplified implementation might always redirect to the home page instead.

</Details>

### Domains

If you want to serve your localized content based on different domains, you can provide a list of mappings between domains and locales via the `domains` setting.

**Examples:**

- `us.example.com/en`
- `ca.example.com/en`
- `ca.example.com/fr`

```tsx filename="config.ts"
import {DomainsConfig} from 'next-intl/routing';

export const locales = ['en', 'fr'] as const;

export const domains: DomainsConfig<typeof locales> = [
  {
    domain: 'us.example.com',
    defaultLocale: 'en',
    // Optionally restrict the locales available on this domain
    locales: ['en']
  },
  {
    domain: 'ca.example.com',
    defaultLocale: 'en'
    // If there are no `locales` specified on a domain,
    // all available locales will be supported here
  }
];
```

**Note that:**

1. You can optionally remove the locale prefix in pathnames by changing the [`localePrefix`](#locale-prefix) setting. E.g. [`localePrefix: 'never'`](/docs/routing#locale-prefix-never) can be helpful in case you have unique domains per locale.
2. If no domain matches, the middleware will fall back to the [`defaultLocale`](/docs/routing/middleware#default-locale) (e.g. on `localhost`).

<Details id="domains-testing">
<summary>How can I locally test if my setup is working?</summary>

Learn more about this in the [locale detection for domain-based routing](/docs/routing/middleware#location-detection-domain) docs.

</Details>
